﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Epsilon.Expr;

namespace Epsilon.Parse
{
    public partial class Parser
    {
        public Expression ParseExpr()
        {
            return ParseExpr100();
        }

        public Expression ParseExpr100()
        {
            Expression ex = ParseExpr150();
            if (MatchPeek(TokenType.Symbol, "!"))
            {
                Token t = CurrentToken();
                Chomp();
                return new TxExpr(ex, ParseExpr100(), t.Line, t.Col);
            }
            return ex;
        }

        public Expression ParseExpr150()
        {
            Expression l = ParseExpr160();
            if (MatchPeek(TokenType.Keyword, "orelse"))
            {
                Token t = CurrentToken();
                Chomp();
                return new ScOpExpr(l, ParseExpr150(), ScOp.OrElse, t.Line, t.Col);
            }
            return l;
        }

        public Expression ParseExpr160()
        {
            Expression l = ParseExpr200();
            if (MatchPeek(TokenType.Keyword, "andalso"))
            {
                Token t = CurrentToken();
                Chomp();
                return new ScOpExpr(l, ParseExpr160(), ScOp.AndAlso, t.Line, t.Col);
            }
            return l;
        }

        public Expression ParseExpr200()
        {
            Expression l = ParseExpr300();
            if (CheckType(TokenType.Symbol)) {
                BinOp op;
                Token t = CurrentToken();
                Chomp();
                switch (t.Value)
                {
                    case "==":
                        op = BinOp.Equals;
                        break;
                    case "/=":
                        op = BinOp.NotEquals;
                        break;
                    case ">=":
                        op = BinOp.GreaterThanOrEqual;
                        break;
                    case ">":
                        op = BinOp.GreaterThan;
                        break;
                    case "<":
                        op = BinOp.LessThan;
                        break;
                    case "<=":
                        op = BinOp.LessThanOrEqual;
                        break;
                    default:
                        UnChomp();
                        return l;
                }
                return new BinOpExpr(ParseExpr300(), l, op, t.Line, t.Col);
            }
            return l;
        }

        public Expression ParseExpr300()
        {
            Expression l = ParseExpr400();
            if (CheckType(TokenType.Symbol))
            {
                Token t = CurrentToken();
                if (t.Value == "++" || t.Value == "--")
                {
                    ListOp op = (t.Value == "++") ? ListOp.Concat : ListOp.Diff;
                    Chomp();
                    Match(TokenType.Symbol, ":");
                    Type type = ParseType();
                    return new ListOpExpr(ParseExpr300(), l, op, type, t.Line, t.Col);
                }
            }
            return l;
        }

        public Expression ParseExpr400()
        {
            Expression r = ParseExpr500();
            Token t = CurrentToken();
            if (MatchPeek(TokenType.Symbol, "+"))
            {
                Chomp();
                return new BinOpExpr(ParseExpr400(), r, BinOp.Plus, t.Line, t.Col);
            }
            else if (MatchPeek(TokenType.Symbol, "-"))
            {
                Chomp();
                return new BinOpExpr(ParseExpr400(), r, BinOp.Minus, t.Line, t.Col);
            }
            else if (MatchPeek(TokenType.Keyword, "bor"))
            {
                Chomp();
                return new BinOpExpr(ParseExpr400(), r, BinOp.BOR, t.Line, t.Col);
            }
            else if (MatchPeek(TokenType.Keyword, "bxor"))
            {
                Chomp();
                return new BinOpExpr(ParseExpr400(), r, BinOp.BXOR, t.Line, t.Col);
            }
            else if (MatchPeek(TokenType.Keyword, "bsl"))
            {
                Chomp();
                return new BinOpExpr(ParseExpr400(), r, BinOp.BSL, t.Line, t.Col);
            }
            else if (MatchPeek(TokenType.Keyword, "bsr"))
            {
                Chomp();
                return new BinOpExpr(ParseExpr400(), r, BinOp.BSR, t.Line, t.Col);
            }
            else if (MatchPeek(TokenType.Keyword, "or"))
            {
                Chomp();
                return new BinOpExpr(ParseExpr400(), r, BinOp.OR, t.Line, t.Col);
            }
            else if (MatchPeek(TokenType.Keyword, "xor"))
            {
                Chomp();
                return new BinOpExpr(ParseExpr400(), r, BinOp.XOR, t.Line, t.Col);
            }
            return r;
        }

        public Expression ParseExpr500()
        {
            Expression r = ParseExpr600();
            Token t = CurrentToken();
            if (MatchPeek(TokenType.Symbol, "*"))
            {
                Chomp();
                return new BinOpExpr(ParseExpr500(), r, BinOp.Mult, t.Line, t.Col);
            }
            else if (MatchPeek(TokenType.Keyword, "band"))
            {
                Chomp();
                return new BinOpExpr(ParseExpr500(), r, BinOp.BAND, t.Line, t.Col);
            }
            else if (MatchPeek(TokenType.Keyword, "and"))
            {
                Chomp();
                return new BinOpExpr(ParseExpr500(), r, BinOp.AND, t.Line, t.Col);
            }
            return r;
        }

        public Expression ParseExpr600()
        {
            Token t = CurrentToken();
            if (MatchPeek(TokenType.Symbol, "+"))
            {
                Chomp();
                return new PreOpExpr(ParseExpr700(), PreOp.Plus, t.Line, t.Col);
            }
            else if (MatchPeek(TokenType.Symbol, "-"))
            {
                Chomp();
                return new PreOpExpr(ParseExpr700(), PreOp.Minus, t.Line, t.Col);
            }
            else if (MatchPeek(TokenType.Keyword, "bnot"))
            {
                Chomp();
                return new PreOpExpr(ParseExpr700(), PreOp.BNOT, t.Line, t.Col);
            }
            else if (MatchPeek(TokenType.Keyword, "not"))
            {
                Chomp();
                return new PreOpExpr(ParseExpr700(), PreOp.NOT, t.Line, t.Col);
            }
            return ParseExpr700();
        }

        public Expression ParseExpr700()
        {
            Token t = CurrentToken();
            Expression e = null;
            if (MatchPeek(TokenType.Symbol, "#"))
                return ParseRecordExpr(null);

            e = ParseExpr800();
            if (MatchPeek(TokenType.Symbol, "#"))
                return ParseRecordExpr(e);
            else if (MatchPeek(TokenType.Symbol, "("))
            {
                Chomp();
                List<Expression> args = new List<Expression>();
                if (!MatchPeek(TokenType.Symbol, ")")) 
                    args = ParseArgExprs();
                Match(TokenType.Symbol, ")");
                return new ApplyExpr(e, args, t.Line, t.Col);
            }

            return e;
        }

        public Expression ParseExpr800()
        {
            Token t = CurrentToken();
            int curPos = pos;
            if (CheckType(TokenType.Atom))
            {
                string name = CurrentToken().Value;
                Chomp();
                if (MatchPeek(TokenType.Symbol, ":"))
                {
                    Chomp();
                    if (CheckType(TokenType.Atom))
                    {
                        name += ":" + CurrentToken().Value;
                        Chomp();
                        return new FnRefExpr(name, t.Line, t.Col);
                    }
                }
            }
            pos = curPos;
            return ParseExprMax();
        }

        public Expression ParseRecordExpr(Expression record)
        {
            Token t = CurrentToken();
            Expression e = null;
            if (record == null)
            {
                Match(TokenType.Symbol, "#");
                if (CheckType(TokenType.Atom))
                {
                    RecordExpr re = new RecordExpr(CurrentToken().Value, t.Line, t.Col);
                    Chomp();
                    Match(TokenType.Symbol, "{");
                    List<RecordField> fields = null;
                    if (!MatchPeek(TokenType.Symbol, "}"))
                        fields = ParseRecordFields();
                    Match(TokenType.Symbol, "}");
                    e = (fields == null) ? (Expression)re : (Expression)(new RecModExpr(re, re.Name, fields, t.Line, t.Col));
                }
                else
                {
                    Error("Expected a record name");
                }

                if (MatchPeek(TokenType.Symbol, "#"))
                    return ParseRecordExpr(e);
            }
            else
            {
                Match(TokenType.Symbol, "#");
                if (CheckType(TokenType.Atom))
                {
                    string name = CurrentToken().Value;
                    Chomp();
                    if (MatchPeek(TokenType.Symbol, "{"))
                    {
                        Chomp();
                        List<RecordField> fields = ParseRecordFields();
                        Match(TokenType.Symbol, "}");
                        e = new RecModExpr(record, name, fields, t.Line, t.Col);
                        if (MatchPeek(TokenType.Symbol, "#"))
                            return ParseRecordExpr(e);
                    }
                    else if (MatchPeek(TokenType.Symbol, "."))
                    {
                        Chomp();
                        if (CheckType(TokenType.Atom))
                        {
                            string field = CurrentToken().Value;
                            Chomp();
                            e = new RecProjExpr(record, name, field, t.Line, t.Col);
                            if (MatchPeek(TokenType.Symbol, "#"))
                                return ParseRecordExpr(e);
                        }
                        else
                        {
                            Error("Expected a field name");
                        }
                    }
                    else
                    {
                        Error("Invalid record expression");
                    }
                }
                else
                {
                    Error("Expected a record name");
                }
            }
            return e;
        }

        public Expression ParseExprMax()
        {
            Token t = CurrentToken();
            if (CheckType(TokenType.Variable))
            {
                Chomp();
                return new VarExpr(t.Value, t.Line, t.Col);
            }
            else if (CheckType(TokenType.Atom))
            {
                Chomp();
                if (MatchPeek(TokenType.Symbol, "/"))
                {
                    Chomp();
                    if (CheckType(TokenType.Integer))
                    {
                        FnRefExpr fn = new FnRefExpr(t.Value + "/" + CurrentToken().Value, t.Line, t.Col);
                        Chomp();
                        return fn;
                    }
                    UnChomp();
                }
                return new AtomExpr(t.Value, t.Line, t.Col);
            }
            else if (CheckType(TokenType.Integer))
            {
                Chomp();
                return new IntExpr(t.Value, t.Line, t.Col);
            }
            else if (CheckType(TokenType.Keyword))
            {
                switch (t.Value)
                {
                    case "true":
                    case "false":
                        Chomp();
                        return new BoolExpr(t.Value, t.Line, t.Col);
                    case "void":
                        Chomp();
                        return new UnitExpr(t.Line, t.Col);
                    case "self":
                        Chomp();
                        return new SelfExpr(t.Line, t.Col);
                    case "begin":
                        Chomp();
                        SeqExpr se = new SeqExpr(ParseExprs(), t.Line, t.Col);
                        Match(TokenType.Keyword, "end");
                        return se;
                    case "pid":
                        Chomp();
                        Match(TokenType.Symbol, "(");
                        PidExpr pe = ParsePid();
                        Match(TokenType.Symbol, ")");
                        return pe;
                    case "element":
                        Chomp();
                        Match(TokenType.Symbol, "(");
                        if (CheckType(TokenType.Integer))
                        {
                            int e = Int32.Parse(CurrentToken().Value);
                            Chomp();
                            Match(TokenType.Symbol, ",");
                            Expression tuple = ParseExpr();
                            Match(TokenType.Symbol, ")");
                            return new ProjExpr(tuple, e, t.Line, t.Col);
                        }
                        else
                        {
                            Error("Expected an integer");
                        }
                        break;
                    case "if":
                        return ParseIfExpr();
                    case "list":
                        return ParseListCaseExpr();
                    case "spawn":
                        return ParseSpawnExpr();
                    case "fun":
                        return ParseFunExpr();
                    case "receive":
                        return ParseRxExpr();
                    default:
                        Error("Invalid expression");
                        break;
                }
            }
            else if (CheckType(TokenType.Symbol))
            {
                switch (t.Value)
                {
                    case "(":
                        Chomp();
                        Expression e = ParseExpr();
                        Match(TokenType.Symbol, ")");
                        return new ParenExpr(e, t.Line, t.Col);
                    case "{":
                        Chomp();
                        List<Expression> es = ParseArgExprs();
                        Match(TokenType.Symbol, "}");
                        return new TupleExpr(es, t.Line, t.Col);
                    case "[":
                        return ParseListExpr();
                    case "<[":
                        return ParseLabExpr();
                    default:
                        Error("Invalid expression");
                        break;
                }
            }
            else if (CheckType(TokenType.String))
            {
                List<string> strings = new List<string>();
                while (CheckType(TokenType.String))
                {
                    strings.Add(CurrentToken().Value);
                    Chomp();
                }
                return new StringExpr(strings, t.Line, t.Col);
            }
            return null;
        }

        public LabExpr ParseLabExpr()
        {
            Token t = CurrentToken();
            Match(TokenType.Symbol, "<[");
            Expression expr = null;
            Token tt = CurrentToken();
            if (MatchPeek(TokenType.Keyword, "true"))
                expr = new BoolExpr(tt.Value, tt.Line, tt.Col);
            else if (MatchPeek(TokenType.Keyword, "false"))
                expr = new BoolExpr(tt.Value, tt.Line, tt.Col);
            else if (CheckType(TokenType.Integer))
                expr = new IntExpr(tt.Value, tt.Line, tt.Col);
            else
                Error("Expected an integer or boolean value");
            Chomp();
            Match(TokenType.Symbol, ",");
            Match(TokenType.Symbol, "{");
            List<Expression> labels = ParseArgExprs();
            Match(TokenType.Symbol, "}");
            Match(TokenType.Symbol, "]>");
            return new LabExpr(expr, labels, t.Line, t.Col);
        }

        public SpawnExpr ParseSpawnExpr()
        {
            Token t = CurrentToken();
            Match(TokenType.Keyword, "spawn");
            Match(TokenType.Symbol, "(");
            Expression fun = ParseExpr();
            Match(TokenType.Symbol, ",");
            List<Expression> args = ParseArgList();
            Match(TokenType.Symbol, ")");
            return new SpawnExpr(fun, args, t.Line, t.Col);
        }

        public List<Expression> ParseArgList()
        {
            Match(TokenType.Symbol, "(");
            if (MatchPeek(TokenType.Symbol, ")"))
            {
                Chomp();
                return new List<Expression>();
            }
            List<Expression> es = ParseArgExprs();
            Match(TokenType.Symbol, ")");
            return es;
        }

        public List<Expression> ParseExprs()
        {
            List<Expression> es = new List<Expression>();
            es.Add(ParseExprLet());
            while (MatchPeek(TokenType.Symbol, ","))
            {
                Chomp();
                es.Add(ParseExprLet());
            }
            return es;
        }

        public Expression ParseExprLet()
        {
            Token t = CurrentToken();
            if (MatchPeek(TokenType.Keyword, "let"))
            {
                Chomp();
                IPattern p = ParsePattern();
                Match(TokenType.Symbol, "=");
                Expression e = ParseExpr();
                return new PatExpr(p, e, t.Line, t.Col);
            }
            else
            {
                return ParseExpr();
            }
        }

        public List<Expression> ParseArgExprs()
        {
            List<Expression> es = new List<Expression>();
            es.Add(ParseExpr());
            while (MatchPeek(TokenType.Symbol, ","))
            {
                Chomp();
                es.Add(ParseExpr());
            }
            return es;
        }

        public IfExpr ParseIfExpr()
        {
            Token t = CurrentToken();
            Match(TokenType.Keyword, "if");
            List<IfClause> clauses = ParseIfClauses();
            Match(TokenType.Symbol, ";");
            BoolExpr b = new BoolExpr("true", CurrentToken().Line, CurrentToken().Col);
            Match(TokenType.Keyword, "true");
            Match(TokenType.Symbol, "->");
            List<Expression> truebody = ParseExprs();
            Match(TokenType.Keyword, "end");
            List<Expression> g = new List<Expression>();
            g.Add(b);
            List<List<Expression>> gg = new List<List<Expression>>();
            gg.Add(g);
            clauses.Add(new IfClause(gg, truebody, b.Line, b.Col));
            return new IfExpr(clauses, t.Line, t.Col);
        }

        public IfClause ParseIfClause() 
        {
            Token t = CurrentToken();
            List<List<Expression>> guard = ParseGuardSeq();
            Match(TokenType.Symbol, "->");
            List<Expression> body = ParseExprs();
            return new IfClause(guard, body, t.Line, t.Col);
        }

        public List<IfClause> ParseIfClauses()
        {
            List<IfClause> clauses = new List<IfClause>();
            clauses.Add(ParseIfClause());
            while (MatchPeek(TokenType.Symbol, ";"))
            {
                Chomp();
                if (MatchPeek(TokenType.Keyword, "true"))
                {
                    UnChomp();
                    break;
                }
                clauses.Add(ParseIfClause());
            }
            return clauses;
        }

        public List<List<Expression>> ParseGuardSeq()
        {
            List<List<Expression>> guardseq = new List<List<Expression>>();
            guardseq.Add(ParseGuard());
            while (MatchPeek(TokenType.Symbol, ";"))
            {
                Chomp();
                guardseq.Add(ParseGuard());
            }
            return guardseq;
        }

        public List<Expression> ParseGuard()
        {
            List<Expression> guard = new List<Expression>();
            guard.Add(ParseExpr());
            while (MatchPeek(TokenType.Symbol, ","))
            {
                Chomp();
                guard.Add(ParseExpr());
            }
            return guard;
        }

        public RecordField ParseRecordField()
        {
            if (CheckType(TokenType.Atom))
            {
                Token t = CurrentToken();
                AtomExpr name = new AtomExpr(t.Value, t.Line, t.Col);
                Chomp();
                Match(TokenType.Symbol, "=");
                Expression contents = ParseExpr();
                return new RecordField(name, contents, t.Line, t.Col);
            }
            else
            {
                Error("Expected a field name");
            }
            return null;
        }

        public List<RecordField> ParseRecordFields()
        {
            List<RecordField> fields = new List<RecordField>();
            Token t = CurrentToken();
            fields.Add(ParseRecordField());
            while (MatchPeek(TokenType.Symbol, ","))
            {
                Chomp();
                fields.Add(ParseRecordField());
            }
            return fields;
        }

        public ListCaseExpr ParseListCaseExpr()
        {
            Token t = CurrentToken();
            Match(TokenType.Keyword, "list");
            Match(TokenType.Keyword, "case");
            Expression list = ParseExpr();
            Match(TokenType.Keyword, "of");
            Match(TokenType.Symbol, "[");
            Match(TokenType.Symbol, "]");
            Match(TokenType.Symbol, "->");
            Expression nil = ParseExpr();
            Match(TokenType.Symbol, "|");
            Match(TokenType.Symbol, "[");
            IPattern head = ParsePattern();
            Match(TokenType.Symbol, "|");
            if (CheckType(TokenType.Variable))
            {
                Token tt = CurrentToken();
                Chomp();
                VarExpr tail = new VarExpr(tt.Value, tt.Line, tt.Col);
                Match(TokenType.Symbol, "]");
                Match(TokenType.Symbol, "->");
                Expression cons = ParseExpr();
                Match(TokenType.Keyword, "end");
                return new ListCaseExpr(list, nil, cons, head, tail, t.Line, t.Col);
            }
            else
            {
                Error("Expected a variable");
            }
            return null;
        }

        public RxExpr ParseRxExpr()
        {
            Token t = CurrentToken();
            Match(TokenType.Keyword, "receive");
            List<RxClauseExpr> clauses = ParseRxClauseExprs();
            Match(TokenType.Keyword, "end");
            return new RxExpr(clauses, t.Line, t.Col);
        }

        public RxClauseExpr ParseRxClauseExpr()
        {
            Token t = CurrentToken();
            IPattern pattern = ParsePattern();
            Match(TokenType.Symbol, ":");
            Type type = ParseType();
            Match(TokenType.Symbol, "->");
            List<Expression> body = ParseExprs();
            return new RxClauseExpr(pattern, type, body, t.Line, t.Col);
        }

        public List<RxClauseExpr> ParseRxClauseExprs()
        {
            List<RxClauseExpr> es = new List<RxClauseExpr>();
            es.Add(ParseRxClauseExpr());
            while (MatchPeek(TokenType.Symbol, ";"))
            {
                Chomp();
                es.Add(ParseRxClauseExpr());
            }
            return es;
        }

        public Expression ParseListExpr()
        {
            Token t = CurrentToken();
            Match(TokenType.Symbol, "[");
            if (MatchPeek(TokenType.Symbol, "]"))
            {
                Chomp();
                Match(TokenType.Symbol, ":");
                Type type = ParseType();
                return new NilExpr(type, t.Line, t.Col);
            }
            else
            {
                List<Expression> elements = new List<Expression>();
                Expression tail = null;
                elements.Add(ParseExpr());
                while (MatchPeek(TokenType.Symbol, ","))
                {
                    Chomp();
                    elements.Add(ParseExpr());
                }
                if (MatchPeek(TokenType.Symbol, "]"))
                {
                    Chomp();
                    return new ListExpr(elements, t.Line, t.Col);
                }
                else if (MatchPeek(TokenType.Symbol, "|"))
                {
                    Chomp();
                    tail = ParseExpr();
                    Match(TokenType.Symbol, "]");
                    return new ListTailExpr(elements, tail, t.Line, t.Col);
                }
                else
                {
                    Error("Malformed list expression");
                }
            }
            return null;
        }
    }
}
